#include <stdlib.h>
#include <sys/param.h> // for MIN/MAX
#include "main.h"
#include "dungeon.h"
#include "mt.h"
#include "rect.h"
#include "graph.h"
#include "map.h"
#include "game.h"
#include "dig.h"
	
#if TOWERHACK_DEBUG_DUNGEON
#include "setup.h"
#include <allegro.h>
#endif

#define SEGQUEUE_SIZE (NUM_DUNGEON_SEGMENTS*2 * MAX_EXITS)

typedef struct {
	Rect area;
	Segment* segment;
} Room;

Room* room; // List of rooms made from segments
int num_rooms;
int active_layer; // Map layer where we are currently planting our rooms

void dungeon_init (Map* map, int num_segments);
void dungeon_cleanup ();
void dig_segment (Segment* s);

#if TOWERHACK_DEBUG_DUNGEON_ANIM
#define SQ_SIZE 10
extern BITMAP* bmp_map;
extern void render_map(int layer);
extern int render_map_hl (coord_t hl1, coord_t hl2);
extern BITMAP* bmp_map;
extern void render_map(int layer);

typedef struct { Rect r; int col; } HL; 
HL highlighter [MAP_WIDTH*MAP_HEIGHT*3];
int num_highlighter = 0;
int blender_set = 0;
extern BITMAP* buffer;
Map* debug_map_ptr;
int zzzz = 0;

void clear_highlighter () { memset (highlighter, 0, sizeof (highlighter)); num_highlighter = 0; }
void highlight (int x, int y, int z, int r, int g, int b) { zzzz = z; highlighter[num_highlighter++] = (HL) { (Rect) {x, y, x+1,y+1,z}, makecol(r,g,b) }; 
	assert (num_highlighter < 4096);}
void highlight_r (Rect r, int red, int g, int blu) { 
	highlighter[num_highlighter++] = (HL) { r, makecol(red,g,blu) }; 
	assert (num_highlighter < 4096);
}

void snapshot (Map* m)
{
	drawing_mode(DRAW_MODE_SOLID, NULL, 0, 0);
	render_map (zzzz);
	acquire_bitmap (buffer);
	blit(bmp_map, buffer, 0, 0, 0, 0, bmp_map->w, bmp_map->h);
	
	if (!blender_set) { set_trans_blender(150, 150, 150, 150); blender_set = 1; }
	drawing_mode(DRAW_MODE_TRANS, NULL, 0, 0);
	for (int i = 0; i < num_highlighter; i++) {
		if (highlighter[i].r.z == zzzz)
		rectfill(buffer, highlighter[i].r.left*SQ_SIZE, highlighter[i].r.top*SQ_SIZE, highlighter[i].r.right*SQ_SIZE, highlighter[i].r.bottom*SQ_SIZE, highlighter[i].col);
	}
	
	release_bitmap (buffer);
	blit(buffer, screen, 0, 0, 0, 0, buffer->w, buffer->h);
	if (!key[KEY_S]) {
		if (key[KEY_D]) rest(50); else if (key[KEY_F]) rest(800); else rest (200);
	}
}
#endif
	
void generate_amazing_maze (Map* m, int seed, int num_segments)
{	
	mt_seed (seed);
	
	Graph g;
	graph_init (&g, num_segments); // Generate a shitload of segments for use in the dungeon
	graph_connect (&g);
	
	dungeon_init (m, g.num_segments);
	Segment* segqueue [SEGQUEUE_SIZE];
	memset (segqueue, 0, SEGQUEUE_SIZE * sizeof (Segment*));
	int next_seg = 0;
	int last_seg = START_PATHS;
	memcpy (segqueue, g.next, START_PATHS * sizeof (Segment*));
	
	Segment* goal_segment = NULL;

	#if TOWERHACK_DEBUG_DUNGEON
	fprintf (stderr, "\nGENERATOR START [seed is %d]\n\n", seed);
	#endif
	#if TOWERHACK_DEBUG_DUNGEON_ANIM
	debug_map_ptr = m;
	#endif
	
	room[0] = (Room) { dig_room (20, 20, (coord_t) {0,0,0}), NULL };
	num_rooms = 1;
		
	Segment* s;
	while (next_seg != last_seg) {
		s = segqueue[next_seg++ % SEGQUEUE_SIZE];
		if (s->layouter_data != 0) continue; // has already been placed
		if (s->type == ST_GOAL) 
		{ 
			goal_segment = s; // remember the goal segment for later
			continue;
		}

		int prerequisites_met = 1;
		for (int i = 0; i < s->num_entrances; i++) {
			if ((s->prev[i] != NULL) && (s->prev[i]->layouter_data == 0)) {
				segqueue[last_seg++ % SEGQUEUE_SIZE] = s;
				prerequisites_met = 0;
				break;
			}
		}
		
		if (prerequisites_met) {
			dig_segment(s);	
			for (int i = 0; i < s->num_exits; i++) {
				if (s->next[i] != NULL) segqueue[last_seg++ % SEGQUEUE_SIZE] = s->next[i];
			}
		}
	}
	
	dig_segment(goal_segment); // Goal segment must be last one to dig
	
	#if TOWERHACK_DEBUG_DUNGEON
	fprintf (stderr, "Dungeon generator done.\n");
	#endif
	
	dungeon_cleanup ();
	graph_cleanup (&g);
}

void dungeon_init (Map* map, int num_segments)
{
	active_layer = 0;
	dig_init (map);
	room = malloc ((1 + num_segments) * sizeof(Room));
	memset (room, 0, (1 + num_segments) * sizeof (Room));
	num_rooms = 0;
}

void dungeon_cleanup ()
{
	dig_cleanup ();
	free (room);
}

void dig_segment (Segment* s) 
{
	int width;
	int height;
	
	switch (s->type) {
	case ST_CORRIDOR:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;
	
	case ST_MULTIPLEXER:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;
	
	case ST_SHORTCUT:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;
	
	case ST_TREASURE:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;
	
	case ST_MONSTER_CHALLENGE:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;
	
	case ST_SWITCH_CHALLENGE:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;
	
	case ST_SOKOBAN_CHALLENGE:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;
	
	case ST_MULTIPLAYER_CHALLENGE:
		switch (mt_rand() % 3) { 
			case 0: width = mt_randint(9, 15); height = 3; break;
			case 1: width = mt_randint (6, 11); height = mt_randint (6, 11); break;
			case 2: width = 3; height = mt_randint(9, 15); break;
		}
		break;

        case ST_GOAL:
                width = MAP_WIDTH / 2;
                height = MAP_HEIGHT / 2;
                break;

        default: assert(0);
	
	}
	
	Room prev_room = room[s->prev[0] ? s->prev[0]->layouter_data : 0];
	Room next_room = { dig_room (width, height, rect_middle(&prev_room.area)), s };
	room[s->layouter_data = num_rooms++] = next_room;

	int is_room (coord_t c) { return 0; } // TODO: Implement blocked room areas
	
	for (int i = 0; i < s->num_entrances; i++) {
		dig_tunnel (rect_middle (&room[(s->prev[i] == NULL) ? 0 : s->prev[i]->layouter_data].area), 
			rect_middle (&room[s->layouter_data].area), &is_room);
	}
}

